package com.lw.sbdemo2.common.utils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

/**
 * <p>标题: XML工具类</p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2018</p>
 * <p>公司: 智业软件</p>
 *
 * @author LinWang
 * @date 2018-05-14 15:37
 */
public class XmlUtil {


    private static XStream xStream;

    /*
     * JVM加载类时会执行这些静态的代码块，如果static代码块有多个，
     * JVM将按照它们在类中出现的先后顺序依次执行它们，每个代码块只会被执行一次
     */
    static {
        xStream = new XStream(new DomDriver());

        //XStream.setupDefaultSecurity(xStream);
        //xStream.allowTypes(new Class[]{Person.class, Address.class});
        /*
         * 使用xStream.alias(String name, Class Type)为任何一个自定义类创建到类到元素的别名
         * 如果不使用别名，则生成的标签名为类全名
         */
        //xStream.alias("person", Person.class);
        //xStream.alias("address", Address.class);

        //将某一个类的属性，作为xml头信息的属性，而不是子节点
        //xStream.useAttributeFor(Address.class, "country");

        //对属性取别名
        //xStream.aliasField("省", Address.class, "province");
    }

    /**
     * Xml字符串 TO JavaBean
     * 常用于接口出参转换
     *
     * @param xmlStr XML字符串
     * @param cls 类类型
     * @return 所需的JavaBean
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T toBean(String xmlStr, Class<T> cls) throws Exception {
        try {
            //注解方式支持
            xStream.processAnnotations(cls);
            return (T) xStream.fromXML(xmlStr);
        } catch (Exception e) {
            throw new Exception("Xml文本转换成对象出错！", e);
        }
    }

    /**
     * JavaBean TO Xml字符串
     * 常用于接口入参转换
     *
     * @param t JavaBean对象
     * @return 所需的XML字符串
     * @throws Exception
     */
    public static <T> String toXml(T t) throws Exception {
        try {
            //注解方式支持
            xStream.processAnnotations(t.getClass());
            return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xStream.toXML(t)
                    .replaceAll("&quot;", "\"");
        } catch (Exception e) {
            throw new Exception("对象转换成Xml文本出错！", e);
        }
    }

    /**
     * Document对象转换XML字符串（利用Dom4j）
     * 适用于入参封装
     */
    public static String docToXml(Document doc) throws Exception {

        String outStr;

        try {
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");
            StringWriter out = new StringWriter(1024);
            XMLWriter xmlWriter = new XMLWriter(out, format);
            xmlWriter.write(doc);
            outStr = out.toString();
        } catch (Exception e) {
            throw new Exception("Document对象转换XML字符串出错！", e);
        }

        return outStr;
    }

    /**
     * Document对象转换XML字符串（利用Dom4j）
     * 适用于入参封装（效率更高？）
     */
    public static String docToXmlT(Document document) throws Exception {
        String s = "";
        try {
            // 使用输出流来进行转化
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            // 使用UTF-8编码
            OutputFormat format = new OutputFormat("   ", true, "UTF-8");
            XMLWriter writer = new XMLWriter(out, format);
            writer.write(document);
            s = out.toString("UTF-8");
        } catch (Exception e) {
            throw new Exception("Document对象转换XML字符串出错！", e);
        }
        return s;
    }

    /**
     * xml schema校验XML（XSD）
     *
     * @param xmlStr xml字符串
     * @param xsdStr xsd字符串
     * @return 错误元素名称和位置
     * @throws Exception
     */
    public static String validateXmlWithXsd(String xmlStr, String xsdStr) throws Exception {
        String errorStr = "";
        Reader xmlReader = null;
        Reader xsdReader = null;
        XMLStreamReader reader = null;
        try {
            xmlReader = new BufferedReader(new StringReader(xmlStr));
            xsdReader = new BufferedReader(new StringReader(xsdStr));
            Source xmlSource = new StreamSource(xmlReader);
            Source xsdSource = new StreamSource(xsdReader);
            // 建立schema工厂
            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            // 利用schema工厂，接收验证xsd文件生成Schema对象
            Schema schema = factory.newSchema(xsdSource);
            // 通过Schema产生针对于此Schema的验证器，利用schenaFile进行验证
            Validator validator = schema.newValidator();

            reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlSource);
            XmlErrorHandler errHandler = new XmlErrorHandler(reader);
            validator.setErrorHandler(errHandler);
            validator.validate(new StAXSource(reader));
            errorStr = errHandler.getErrorText();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (XMLStreamException e) {
                    e.printStackTrace();
                }
            }

            if (xsdReader != null) {
                try {
                    xsdReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (xmlReader != null) {
                try {
                    xmlReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return errorStr;
    }
}

